﻿using System.Diagnostics;
using System.Text;
using MoqaLate.CodeModel;
using MoqaLate.Common;

namespace MoqaLate.MockClassBuilding
{
    public class ClassTextBuilder : IClassTextBuilder
    {
        private readonly ILogger _logger;

        public ClassTextBuilder(ILogger logger)
        {
            _logger = logger;
        }

        #region IClassTextBuilder Members

        public string Create(ClassSpecification spec)
        {
            var sb = new StringBuilder();

            _logger.Write(string.Format("Starting to create output for class '{0}'", spec.ClassName));

            AppendUsings(sb, spec);

            sb.AppendLine("namespace MoqaLate.Autogenerated");
            sb.AppendLine("{");

            sb.AppendLine("public partial class " + spec.ClassName + spec.InterfaceGenericTypes + " : " + spec.OriginalInterfaceName + spec.InterfaceGenericTypes);
            sb.AppendLine("{"); // end class

            foreach (var prop in spec.Properties)
            {
                AppendProperty(sb, prop);
            }

            foreach (var method in spec.Methods)
            {
                AppendMethod(sb,method);
            }

        
            foreach (var eventSpec in spec.Events)
            {
                AppendEvent(sb, eventSpec);
            }

            sb.AppendLine("}"); // end class

            sb.AppendLine("}"); // end namespace

            var classText = sb.ToString();

            Debug.Write(classText);

            return classText;
        }



        #endregion


        private void AppendEvent(StringBuilder sb, Event eventSpec)
        {
            sb.AppendLine("public virtual event " + eventSpec.Type + " " + eventSpec.Name + ";");

        }


        private void AppendMethod(StringBuilder sb, Method method)
        {

            string template;

            if (method.ReturnType != "void")
            {
                template =
                    @"

// -------------- {0} ------------ 

private {1} _{6}ReturnValue;

        private int _{6}NumberOfTimesCalled;

        {3}
        
        public virtual void {0}SetReturnValue({1} value)
        {{
            _{6}ReturnValue = value;
        }}    


        {5} 

             public {1} {0}({2})
        {{
            _{6}NumberOfTimesCalled++;            

            {4}

            return _{6}ReturnValue;
        }}";
            }
            else
            {
                template =
                    @"

// -------------- {0} ------------


        private int _{6}NumberOfTimesCalled;        

        {3}

        {5}

        public {1} {0}({2})
        {{
            _{6}NumberOfTimesCalled++;            

            {4}
        }}
";
            }


            sb.AppendFormat(template, method.Name, method.ReturnType,
                method.Parameters, CreateLastCalledWithFields(method),
                CreateLastCalledWithPropValSetters(method), CreateWasCalledWithMethods(method),
                LowescaseFirstLetter(method.Name));

        }

        private string LowescaseFirstLetter(string s)
        {
            return s[0].ToString().ToLower() + s.Substring(1);
        }

        private string CreateWasCalledWithMethods(Method method)
        {
     


            var sb = new StringBuilder();

           
            // WasCalled
            sb.AppendFormat("public virtual bool {0}WasCalled()", method.Name);
            sb.AppendLine("");
            sb.AppendLine("{");
            sb.AppendFormat("   return _{0}NumberOfTimesCalled > 0;",LowescaseFirstLetter(method.Name));
            sb.AppendLine("");
            sb.AppendLine("}");

            sb.AppendLine("");
            sb.AppendLine("");

            // WasCalledTime
            sb.AppendFormat("public virtual bool {0}WasCalled(int times)", method.Name);
            sb.AppendLine("");
            sb.AppendLine("{");
            sb.AppendFormat("   return _{0}NumberOfTimesCalled == times;", LowescaseFirstLetter(method.Name));
            sb.AppendLine("");
            sb.AppendLine("}");


            sb.AppendLine("");
            sb.AppendLine("");



            // timescalles
            sb.AppendFormat("public virtual int {0}TimesCalled()", method.Name);
            sb.AppendLine("");
            sb.AppendLine("{");
            sb.AppendFormat("   return _{0}NumberOfTimesCalled;", LowescaseFirstLetter(method.Name));
            sb.AppendLine("");
            sb.AppendLine("}");


            sb.AppendLine("");
            sb.AppendLine("");




            if (method.Parameters.Count == 0) // TECHDEBT: need to refactor this method into smaller ones
                return sb.ToString(); 

            // WasCalledWith
            sb.AppendFormat("public virtual bool {0}WasCalledWith({1})", method.Name, method.Parameters);
            sb.AppendLine("{");

            sb.AppendLine("return (");

            for (var i = 0; i < method.Parameters.Count; i++)
            {
                sb.AppendFormat("{0}.Equals({1}Parameter_{0}_LastCalledWith) ", method.Parameters[i].Name, method.Name);

                if (i < method.Parameters.Count -1)
                    sb.AppendLine(" && ");
            }

            sb.AppendLine(");");
            sb.AppendLine("}");
           

            return sb.ToString();
        }


        private string CreateLastCalledWithFields(Method method)
        {
            if (method.Parameters.Count == 0)
                return string.Empty;


            var sb = new StringBuilder();

          //  sb.AppendLine("// ----------------- start CreateLastCalledWithFields");

            method.Parameters.ForEach(
                x => sb.AppendFormat("public {0} {1}Parameter_{2}_LastCalledWith;", x.Type, method.Name, x.Name));

        //    sb.AppendLine("// ----------------- end CreateLastCalledWithFields");
            return sb.ToString();


        }

        

        private string CreateLastCalledWithPropValSetters(Method method)
        {
            if (method.Parameters.Count == 0)
                return string.Empty;

            var sb = new StringBuilder();

          //  sb.AppendLine("// ----------------- start CreateLastCalledWithPropValSetters");

            method.Parameters.ForEach(
                x => sb.AppendFormat("{0}Parameter_{1}_LastCalledWith = {1};", method.Name, x.Name));


        //    sb.AppendLine("// ----------------- end CreateLastCalledWithPropValSetters");
            return sb.ToString();
        }




        private void AppendUsings(StringBuilder sb, ClassSpecification spec)
        {
            foreach (var ns in spec.Usings)
            {
                sb.AppendLine("using " + ns + ";");
            }

            if (! string.IsNullOrWhiteSpace(spec.OriginalInterfaceNamespace))
                sb.AppendLine("using " + spec.OriginalInterfaceNamespace + ";");
        }

        private void AppendProperty(StringBuilder sb, Property prop)
        {
            _logger.Write(string.Format("Starting to write property text for property '{0}'", prop.Name));

            sb.AppendLine(string.Format(@"// ------------ Property {0}", prop.Name));

            // backing field
            var backingFieldName = "_" + prop.Name;
            sb.AppendLine("private " + prop.Type + " " + backingFieldName + ";");


            // prop body
            sb.AppendLine("public virtual " + prop.Type + " " + prop.Name);
            sb.AppendLine("{");

            if (prop.Accessor == PropertyAccessor.GetOny || prop.Accessor == PropertyAccessor.GetAndSet)
                sb.AppendLine(string.Format("get {{ return {0}; }}", backingFieldName));

            if (prop.Accessor == PropertyAccessor.SetOny || prop.Accessor == PropertyAccessor.GetAndSet)
                sb.AppendLine(string.Format("set {{ {0} = value; }}", backingFieldName));

            sb.AppendLine("}");


            _logger.Write(string.Format("Starting to write property shortcut text for property '{0}'", prop.Name));

            sb.AppendLine(string.Format("public virtual void __Set{0}({1} val)", prop.Name, prop.Type));
            sb.AppendLine("{");
            sb.AppendLine(string.Format("   {0} = val;", backingFieldName));
            sb.AppendLine("}");
        }
    }
}